// Copyright (C) 2012 Volkov Zachar
// This file is part of the "zAxis Game Engine".
// For conditions of distribution and use, see copyright notice zx_Common.hpp

#include "zx_Common.hpp"

namespace zx {

//----------------------------------------------------------------------------//
//
//----------------------------------------------------------------------------//

String Format( const char* _format, ... )
{
     return FormatVL( _format, (char *)&_format + sizeof( &_format ) );
}
//----------------------------------------------------------------------------//
String FormatVL( const char* _format, va_list _args )
{
//this code from ptypes: pputf.cxx

     struct
     {
          bool operator()( char _ch )
          {
               return ( _ch == ' ' ) ||
                      ( _ch == '#' ) ||
                      ( _ch == '+' ) ||
                      ( _ch == '~' ) ||
                      ( _ch == '-' ) ||
                      ( _ch == '.' ) ||
                      ( _ch >= '0' && _ch <= '9' );
          }
         
     } _check_fmtopts;
     

     enum fmt_type_t
     {
         FMT_NONE,
         FMT_CHAR,
         FMT_SHORT,
         FMT_INT,
         FMT_LONG,
         FMT_LARGE,
         FMT_STR,
         FMT_PTR,
         FMT_DOUBLE,
         FMT_LONG_DOUBLE,
         FMT_IPADDR,
         FMT_TIME,
         FMT_LONGTIME
     };

    String _res;
    const char* p = _format;
    while (*p != 0)
    {
        // write out raw data between format specifiers
        const char* e = strchr(p, '%');
        if (e == 0)
            e = p + strlen(p);
        if (e > p)
            _res.append(p, e - p);

        if (*e != '%')
            break;

        e++;
        if (*e == '%')
        {
            // write out a single '%'
            _res.push_back('%');
            p = e + 1;
            continue;
        }

        // build a temporary buffer for the conversion specification
        char fbuf[128];
        fbuf[0] = '%';
        char* f = fbuf + 1;
        bool modif = false;

        // formatting flags and width specifiers
        while ( _check_fmtopts(*e) && uint(f - fbuf) < sizeof(fbuf) - 5)
        {
            *f++ = *e++;
            modif = true;
        }

        // prefixes
        fmt_type_t fmt_type = FMT_NONE;
        switch(*e)
        {
        case 'h': 
            fmt_type = FMT_SHORT; 
            *f++ = *e++;
            break;
        case 'L': 
            fmt_type = FMT_LONG_DOUBLE; 
            *f++ = *e++;
            break;
        case 'l':
            e++;
            if (*e == 'l')
            {
#if defined(_MSC_VER) || defined(__BORLANDC__)
                *f++ = 'I';
                *f++ = '6';
                *f++ = '4';
#else
                *f++ = 'l';
                *f++ = 'l';
#endif
                e++;
                fmt_type = FMT_LARGE;
            }
            else
            {
                *f++ = 'l';
                fmt_type = FMT_LONG;
            }
            break;
        }

        // format specifier
        switch(*e)
        {
        case 'c':
            fmt_type = FMT_CHAR;
            *f++ = *e++;
            break;
        case 'd':
        case 'i':
        case 'o':
        case 'u':
        case 'x':
        case 'X':
            if (fmt_type < FMT_SHORT || fmt_type > FMT_LARGE)
                fmt_type = FMT_INT;
            *f++ = *e++;
            break;
        case 'e':
        case 'E':
        case 'f':
        case 'g':
        case 'G':
            if (fmt_type != FMT_LONG_DOUBLE)
                fmt_type = FMT_DOUBLE;
            *f++ = *e++;
            break;
        case 's':
            fmt_type = FMT_STR;
            *f++ = *e++;
            break;
        case 'p':
            fmt_type = FMT_PTR;
            *f++ = *e++;
            break;
        case 'a':
            fmt_type = FMT_IPADDR;
            *f++ = *e++;
            break;
        case 't':
            fmt_type = FMT_TIME;
            *f++ = *e++;
            break;
        case 'T':
            fmt_type = FMT_LONGTIME;
            *f++ = *e++;
            break;
        }

        if (fmt_type == FMT_NONE)
            break;

        *f = 0;

        // some formatters are processed here 'manually',
        // while others are passed to snprintf
        char buf[4096];
        int s = 0;
        switch(fmt_type)
        {
        case FMT_NONE: 
            break; // to avoid compiler warning
        case FMT_CHAR:
            if (modif)
                s = _snprintf(buf, sizeof(buf), fbuf, va_arg(_args,int)); 
            else
                _res.push_back(char(va_arg(_args,int)));
            break;         
        case FMT_SHORT:
            s = _snprintf(buf, sizeof(buf), fbuf, va_arg(_args,int)); 
            break;
        case FMT_INT:
            s = _snprintf(buf, sizeof(buf), fbuf, va_arg(_args,int)); 
            break;
        case FMT_LONG:
            s = _snprintf(buf, sizeof(buf), fbuf, va_arg(_args,long)); 
            break;
        case FMT_LARGE:
            s = _snprintf(buf, sizeof(buf), fbuf, va_arg(_args,int64)); 
            break;
        case FMT_STR:
            if (modif)
                s = _snprintf(buf, sizeof(buf), fbuf, va_arg(_args,char*));
            else
                _res.append(va_arg(_args,const char*));
            break;
        case FMT_PTR:
            s = _snprintf(buf, sizeof(buf), fbuf, va_arg(_args,void*)); 
            break;
        case FMT_DOUBLE:
            s = _snprintf(buf, sizeof(buf), fbuf, va_arg(_args,double)); 
            break;
        case FMT_LONG_DOUBLE:
            s = _snprintf(buf, sizeof(buf), fbuf, va_arg(_args,long double)); 
            break;

        case FMT_IPADDR:
            {
                union { long l; uint8 b[4]; } ip = { va_arg(_args,long) };
                s = _snprintf(buf, sizeof(buf), "%d.%d.%d.%d", 
                    uint(ip.b[0]), uint(ip.b[1]), uint(ip.b[2]), uint(ip.b[3]));
            }
            break;
        
        //case FMT_TIME:
        //case FMT_LONGTIME:
        //    {
        //        char* fmt = fmt_type == FMT_TIME ? shorttimefmt : longtimefmt;
        //        struct tm t;
        //        int64 dt = va_arg(va,int64);
        //        if (dt < 0)
        //            dt = 0;
        //        s = strftime(buf, sizeof(buf), fmt, dttotm(dt, t));
        //    }
        //    break;
        }
        if (s > 0)
            _res.append(buf, s);
        
        p = e;
    }

     return _res;
}
//----------------------------------------------------------------------------//
//----------------------------------------------------------------------------//

} // namespace zx
